#include <srs_app_security.hpp>

#include <srs_kernel_error.hpp>
#include <srs_app_config.hpp>

using namespace std;

SrsSecurity::SrsSecurity() {
}

SrsSecurity::~SrsSecurity() {
}

int SrsSecurity::check(SrsRtmpConnType type, const string& ip, SrsRequest* req) {
    int ret = ERROR_SUCCESS;
    // allow all if security disabled.
    auto phost = _srs_config->get_vhost(req->vhost);
    if (!_srs_config->get_security_enabled(req->vhost, phost)) {
        return ret;
    }

    // default to deny all when security enabled.
    ret = ERROR_SYSTEM_SECURITY;
    // rules to apply
    auto rules = _srs_config->get_security_rules(req->vhost, phost);
    if (rules == nullptr) {
        return ret;
    }

    // allow if matches allow strategy.
    if (allow_check(rules, type, ip) == ERROR_SYSTEM_SECURITY_ALLOW) {
        ret = ERROR_SUCCESS;
    }

    // deny if matches deny strategy.
    if (deny_check(rules, type, ip) == ERROR_SYSTEM_SECURITY_DENY) {
        ret = ERROR_SYSTEM_SECURITY_DENY;
    }
    return ret;
}

int SrsSecurity::allow_check(SrsConfDirective* rules, SrsRtmpConnType type, const string& ip) {
    int ret = ERROR_SUCCESS;
    for (int i = 0; i < (int)rules->directives.size(); i++) {
        auto rule = rules->at(i);
        if (rule == nullptr) {
            continue;
        }
        if (rule->name != "allow") {
            continue;
        }
        switch (type) {
            case SrsRtmpConnPlay: {
                if (rule->arg0() != "play") {
                    break;
                }
                if (rule->arg1() == "all" || rule->arg1() == ip) {
                    ret = ERROR_SYSTEM_SECURITY_ALLOW;
                    break;
                }
            } break;
            case SrsRtmpConnFMLEPublish:
            case SrsRtmpConnFlashPublish:
            case SrsRtmpConnHaivisionPublish: {
                if (rule->arg0() != "publish") {
                    break;
                }
                if (rule->arg1() == "all" || rule->arg1() == ip) {
                    ret = ERROR_SYSTEM_SECURITY_ALLOW;
                    break;
                }
            } break;
            case SrsRtmpConnUnknown:
            default:
                break;
        }

        // when matched, donot search more.
        if (ret == ERROR_SYSTEM_SECURITY_ALLOW) {
            break;
        }
    }
    return ret;
}

int SrsSecurity::deny_check(SrsConfDirective* rules, SrsRtmpConnType type, const string& ip) {
    int ret = ERROR_SUCCESS;
    for (int i = 0; i < (int)rules->directives.size(); i++) {
        auto rule = rules->at(i);
        if (rule == nullptr) {
            continue;
        }
        if (rule->name != "deny") {
            continue;
        }
        switch (type) {
            case SrsRtmpConnPlay: {
                if (rule->arg0() != "play") {
                    break;
                }
                if (rule->arg1() == "all" || rule->arg1() == ip) {
                    ret = ERROR_SYSTEM_SECURITY_DENY;
                    break;
                }
            } break;
            case SrsRtmpConnFMLEPublish:
            case SrsRtmpConnFlashPublish:
            case SrsRtmpConnHaivisionPublish: {
                if (rule->arg0() != "publish") {
                    break;
                }
                if (rule->arg1() == "all" || rule->arg1() == ip) {
                    ret = ERROR_SYSTEM_SECURITY_DENY;
                    break;
                }
            } break;
            case SrsRtmpConnUnknown:
            default:
                break;
        }

        // when matched, donot search more.
        if (ret == ERROR_SYSTEM_SECURITY_DENY) {
            break;
        }
    }
    return ret;
}
